#include "mmu.h"

byte* memory;

byte* rom0;
byte* rom1;
byte* vram;
byte* sram;
byte* wram;
byte* oam;
byte* io;
byte* zram;

FILE* rom;

void mmu_init() {
	memory = malloc(0xFFFF);
	if (memory == NULL) {
		printf("malloc failed");
		exit(-1);
	}
	rom0 = memory;
	rom1 = memory + 0x4000;
	vram = memory + 0x8000;
	sram = memory + 0xA000;
	wram = memory + 0xC000;
	oam = memory + 0xFE00;
	io = memory + 0xFF00;
	zram = memory + 0xFF80;
	
	mmu_load_rom();
}

void mmu_load_rom() {
	if ((rom = fopen("rom.gb", "rb")) == NULL ) {
		printf("read failed");
	}
	fread(rom0, 1, 0x4000, rom);
	fread(rom1, 1, 0x4000, rom);
}

byte rb(word adr) {
	switch (adr & 0xF000) {
		
		case 0x0000:
		case 0x1000:
		case 0x2000:
		case 0x3000:
			return *(rom0 + adr);
		case 0x4000:
		case 0x5000:
		case 0x6000:
		case 0x7000:
			return *(rom1 + (adr & 0x3FFF));
		case 0x8000:
		case 0x9000:
			return *(vram + (adr & 0x1FFF));
		case 0xA000:
		case 0xB000:
			return *(sram + (adr & 0x1FFF));
		case 0xC000:
		case 0xD000:
			return *(wram + (adr & 0x1FFF));
		case 0xE000:
			return *(wram + (adr & 0x1FFF));
		case 0xF000:
			switch(adr & 0x0F00) {
				case 0x0000: case 0x0100: case 0x0200: case 0x0300:
				case 0x0500: case 0x0600: case 0x0700: case 0x0800:
				case 0x0900: case 0x0A00: case 0x0B00: case 0x0C00:
				case 0x0D00: 
					return *(wram + (adr & 0x1FFF));
				case 0x0E00:
					if (adr < 0xFEA0) {
						return *(oam + (adr &0x00FF));
					}
					else return 0;
				case 0x0F00:
					if (adr < 0xFF80) {
						
					}
					else {
						return *(zram + (adr & 0x007F));
					}
			}
	}
}

void wb(word adr, byte data) {
	switch (adr & 0xF000) {
		
		case 0x0000:
		case 0x1000:
		case 0x2000:
		case 0x3000:
			*(rom0 + adr) = data;
		case 0x4000:
		case 0x5000:
		case 0x6000:
		case 0x7000:
			*(rom1 + (adr & 0x3FFF)) = data;
		case 0x8000:
		case 0x9000:
			*(vram + (adr & 0x1FFF)) = data;
		case 0xA000:
		case 0xB000:
			*(sram + (adr & 0x1FFF)) = data;
		case 0xC000:
		case 0xD000:
			*(wram + (adr & 0x1FFF)) = data;
		case 0xE000:
			*(wram + (adr & 0x1FFF)) = data;
		case 0xF000:
			switch(adr & 0x0F00) {
				case 0x0000: case 0x0100: case 0x0200: case 0x0300:
				case 0x0500: case 0x0600: case 0x0700: case 0x0800:
				case 0x0900: case 0x0A00: case 0x0B00: case 0x0C00:
				case 0x0D00: 
					*(wram + (adr & 0x1FFF)) = data;
				case 0x0E00:
					if (adr < 0xFEA0) {
						*(oam + (adr &0x00FF)) = data;
					}
					else 0;
				case 0x0F00:
					if (adr < 0xFF80) {
						
					}
					else {
						*(zram + (adr & 0x007F)) = data;
					}
			}
	}
}

word rw(word adr) {
	switch (adr & 0xF000) {
		
		case 0x0000:
		case 0x1000:
		case 0x2000:
		case 0x3000:
			return (*(rom0 + adr + 1) << 8) + *(rom0 + adr);
		case 0x4000:
		case 0x5000:
		case 0x6000:
		case 0x7000:
			return (*(rom1 + (adr & 0x3FFF) + 1) << 8) + *(rom1 + (adr & 0x3FFF));
		case 0x8000:
		case 0x9000:
			return (*(vram + (adr & 0x1FFF) + 1) << 8) + *(vram + (adr & 0x1FFF));
		case 0xA000:
		case 0xB000:
			return (*(sram + (adr & 0x1FFF) + 1) << 8) + *(sram + (adr & 0x1FFF));
		case 0xC000:
		case 0xD000:
			return (*(wram + (adr & 0x1FFF) + 1) << 8) + *(wram + (adr & 0x1FFF));
		case 0xE000:
			return (*(wram + (adr & 0x1FFF) + 1) << 8) + *(wram + (adr & 0x1FFF));
		case 0xF000:
			switch(adr & 0x0F00) {
				case 0x0000: case 0x0100: case 0x0200: case 0x0300:
				case 0x0500: case 0x0600: case 0x0700: case 0x0800:
				case 0x0900: case 0x0A00: case 0x0B00: case 0x0C00:
				case 0x0D00: 
					return (*(wram + (adr & 0x1FFF) + 1) << 8) + *(wram + (adr & 0x1FFF));
				case 0x0E00:
					if (adr < 0xFEA0) {
						return (*(oam + (adr & 0x00FF) + 1) << 8) + *(oam + (adr & 0x00FF));
					}
					else return 0;
				case 0x0F00:
					if (adr < 0xFF80) {
						return 0;
					}
					else {
						return (*(zram + (adr & 0x007F) + 1) << 8) + *(zram + (adr & 0x007F));
					}
			}
	}
}

void ww(word adr, word data) {
	switch (adr & 0xF000) {
		
		case 0x0000:
		case 0x1000:
		case 0x2000:
		case 0x3000:
			*((word*)(rom0 + adr)) = data;
		case 0x4000:
		case 0x5000:
		case 0x6000:
		case 0x7000:
			*((word*)(rom1 + (adr & 0x3FFF))) = data;
		case 0x8000:
		case 0x9000:
			*((word*)(vram + (adr & 0x1FFF))) = data;
		case 0xA000:
		case 0xB000:
			*((word*)(sram + (adr & 0x1FFF))) = data;
		case 0xC000:
		case 0xD000:
			*((word*)(wram + (adr & 0x1FFF))) = data;
		case 0xE000:
			*((word*)(wram + (adr & 0x0FFF))) = data;
		case 0xF000:
			switch(adr & 0x0F00) {
				case 0x0000: case 0x0100: case 0x0200: case 0x0300:
				case 0x0500: case 0x0600: case 0x0700: case 0x0800:
				case 0x0900: case 0x0A00: case 0x0B00: case 0x0C00:
				case 0x0D00: 
					*((word*)(wram + (adr & 0x1FFF))) = data;
				case 0x0E00:
					if (adr < 0xFEA0) {
						*((word*)(oam + (adr & 0x00FF))) = data;
					}
					else 0;
				case 0x0F00:
					if (adr < 0xFF80) {
						0;
					}
					else {
						*((word*)(zram + (adr & 0x007F))) = data;
					}
			}
	}
}
